/*
package com.team.coupon.util;

import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import com.team.coupon.domain.BizCoupon;
import com.team.coupon.service.BizCouponService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;

import static com.team.coupon.util.RedisConstants.BIZ_COUPON_SECKILL_KEY;

*/
/**
 * 数据同步任务类，负责定时将数据库中的优惠券数据同步到Redis
 * 确保缓存数据与数据库数据的一致性，提高系统性能
 *//*

@Component
public class DataSyncTask {

    private static final Logger logger = LoggerFactory.getLogger(DataSyncTask.class);

    @Autowired
    private BizCouponService bizCouponService; // 优惠券业务逻辑服务

    @Autowired
    private StringRedisTemplate redisTemplate; // Redis操作模板

    @Value("${data.sync.batch.size:1000}")
    private int batchSize; // 批处理大小，从配置文件读取，默认1000条

    @Value("${data.sync.retry.maxAttempts:3}")
    private int maxRetryAttempts; // Redis操作最大重试次数

    @Value("${data.sync.retry.delay:1000}")
    private long retryDelay; // 重试延迟时间(毫秒)

    */
/**
     * 定时任务：每天凌晨0点执行数据同步
     * cron表达式：秒 分 时 日 月 周
     * 0 0 0 * * ? 表示每天0点0分0秒执行
     *//*

    @Scheduled(cron = "0 12 12 * * ?")
    public void syncDataToRedis() {
        // 获取当前时间(上海时区)
        LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
        String format = DateUtil.format(now, "yyyy-MM-dd");

        // 获取昨天的时间
        LocalDateTime yesterday = now.minusDays(1);
        String yesterdayFormat = DateUtil.format(yesterday, "yyyy-MM-dd");

        // 1. 删除昨天的Redis数据，清理过期缓存
        try {
            deleteRedisData(BIZ_COUPON_SECKILL_KEY + yesterdayFormat);
        } catch (Exception e) {
            logger.error("删除Redis历史数据失败: {}", e.getMessage(), e);
        }

        // 2. 同步今日数据到Redis
        try {
            syncTodayData(format);
        } catch (Exception e) {
            logger.error("数据同步失败: {}", e.getMessage(), e);
        }
    }

    */
/**
     * 删除Redis中的数据，带有重试机制
     * @param key Redis键
     *//*

    @Retryable(
            value = {RedisConnectionFailureException.class}, // 指定需要重试的异常类型
            maxAttemptsExpression = "#{${data.sync.retry.maxAttempts}}", // 最大重试次数
            backoff = @Backoff(delayExpression = "#{${data.sync.retry.delay}}") // 重试间隔策略
    )
    public void deleteRedisData(String key) {
        try {
            Boolean deleted = redisTemplate.delete(key);
            logger.info("删除Redis数据 {}，结果: {}", key, deleted);
        } catch (RedisConnectionFailureException e) {
            logger.error("Redis连接失败，删除数据失败: {}", key, e);
            throw e; // 抛出异常触发重试
        } catch (Exception e) {
            logger.error("删除Redis数据失败: {}", key, e);
            throw e;
        }
    }

    */
/**
     * 同步指定日期的数据，支持分批处理
     * @param dateFormat 日期格式字符串
     *//*

    private void syncTodayData(String dateFormat) {
        logger.info("开始同步日期: {}", dateFormat);

        int page = 0;
        boolean hasMoreData = true;

        try {
            // 分页查询数据，避免一次性加载过多数据
            while (hasMoreData) {
                // 分批查询数据，每次查询batchSize条记录
                int offset = page * batchSize;
                List<BizCoupon> dataList = bizCouponService.queryCouponByTime(dateFormat, offset, batchSize);

                if (dataList == null || dataList.isEmpty()) {
                    hasMoreData = false;
                    logger.info("没有更多数据需要同步");
                    continue;
                }
                // 同步当前批次数据
                syncBatchData(dateFormat, dataList);
                logger.info("已同步批次 {}，记录数: {}", page, dataList.size());
                page++;
            }
            logger.info("数据同步完成，日期: {}", dateFormat);
        } catch (Exception e) {
            logger.error("分批同步数据失败，日期: {}", dateFormat, e);
            throw e;
        }
    }

    */
/**
     * 同步单个批次的数据到Redis
     * @param dateFormat 日期格式字符串
     * @param dataList 数据列表
     *//*

    private void syncBatchData(String dateFormat, List<BizCoupon> dataList) {
        try {
            String redisKey = BIZ_COUPON_SECKILL_KEY + dateFormat;

            // 使用List类型，每次追加新数据
            // 序列化数据为JSON字符串
            for (BizCoupon coupon : dataList) {
                redisTemplate.opsForList().rightPush(redisKey, JSONUtil.toJsonStr(coupon));
            }
            logger.info("同步批次数据完成，日期: {}, 记录数: {}", dateFormat, dataList.size());
        } catch (Exception e) {
            logger.error("JSON序列化或Redis操作失败，记录数: {}", dataList.size(), e);
            throw e;
        }
    }
}*/

package com.team.coupon.util;

import cn.hutool.core.date.DateUtil;
import cn.hutool.json.JSONUtil;
import com.team.coupon.domain.BizCoupon;
import com.team.coupon.service.BizCouponService;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.RedisConnectionFailureException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

// MODIFIED: 新增导入
import static com.team.coupon.util.RedisConstants.*;

/**
 * 数据同步任务类，负责定时将数据库中的优惠券数据同步到Redis
 * 确保缓存数据与数据库数据的一致性，提高系统性能
 */
@Slf4j
@Component
public class DataSyncTask {


    @Autowired
    private BizCouponService bizCouponService; // 优惠券业务逻辑服务

    @Autowired
    private StringRedisTemplate redisTemplate; // Redis操作模板

    @Value("${data.sync.batch.size:1000}")
    private int batchSize; // 批处理大小，从配置文件读取，默认1000条

    @Value("${data.sync.retry.maxAttempts:3}")
    private int maxRetryAttempts; // Redis操作最大重试次数

    @Value("${data.sync.retry.delay:1000}")
    private long retryDelay; // 重试延迟时间(毫秒)

    // MODIFIED: 新增线程池，用于并行处理数据
    private final ExecutorService executorService = Executors.newFixedThreadPool(10);

    /**
     * 定时任务：每天凌晨0点执行数据同步
     * cron表达式：秒 分 时 日 月 周
     * 0 0 0 * * ? 表示每天0点0分0秒执行
     */
    @Scheduled(cron = "0 24 15 * * ?")
    public void syncDataToRedis() {
        // 获取当前时间(上海时区)
        LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
        String format = DateUtil.format(now, DATE_FORMAT);

        // 获取昨天的时间
        LocalDateTime yesterday = now.minusDays(1);
        String yesterdayFormat = DateUtil.format(yesterday, DATE_FORMAT);

        // 1. 删除昨天的Redis数据，清理过期缓存
        try {
            // MODIFIED: 修改方法参数和实现逻辑
            deleteRedisData(yesterdayFormat);
        } catch (Exception e) {
            log.error("删除Redis历史数据失败: {}", e.getMessage(), e);
        }

        // 2. 同步今日数据到Redis
        try {
            syncTodayData(format);
        } catch (Exception e) {
            log.error("数据同步失败: {}", e.getMessage(), e);
        }
    }

    /**
     * 删除指定日期的Redis数据，带有重试机制
     * @param dateFormat 日期格式字符串
     */
    @Retryable(
            value = {RedisConnectionFailureException.class}, // 指定需要重试的异常类型
            maxAttemptsExpression = "#{${data.sync.retry.maxAttempts}}", // 最大重试次数
            backoff = @Backoff(delayExpression = "#{${data.sync.retry.delay}}") // 重试间隔策略
    )
    // MODIFIED: 修改方法签名和实现
    public void deleteRedisData(String dateFormat) {
        try {
            // 删除具体日期的优惠券Hash数据
            String couponHashKey = getCouponHashKey(dateFormat);
            Boolean deleted = redisTemplate.delete(couponHashKey);
            log.info("删除Redis优惠券数据 {}，结果: {}", couponHashKey, deleted);

            // 从日期集合中移除该日期
            redisTemplate.opsForSet().remove(ALL_COUPON_DATES_KEY, dateFormat);
            log.info("从日期集合中移除日期 {}", dateFormat);
        } catch (RedisConnectionFailureException e) {
            log.error("Redis连接失败，删除数据失败: {}", dateFormat, e);
            throw e; // 抛出异常触发重试
        } catch (Exception e) {
            log.error("删除Redis数据失败: {}", dateFormat, e);
            throw e;
        }
    }

    /**
     * 同步指定日期的数据，支持分批处理
     * @param dateFormat 日期格式字符串
     */
    private void syncTodayData(String dateFormat) {
        log.info("开始同步日期: {}", dateFormat);

        int page = 0;
        boolean hasMoreData = true;

        try {
            // 分页查询数据，避免一次性加载过多数据
            while (hasMoreData) {
                // 分批查询数据，每次查询batchSize条记录
                int offset = page * batchSize;
                List<BizCoupon> dataList = bizCouponService.queryCouponByTime(dateFormat, offset, batchSize);

                if (dataList == null || dataList.isEmpty()) {
                    hasMoreData = false;
                    log.info("没有更多数据需要同步");
                    continue;
                }
                // 同步当前批次数据
                syncBatchData(dateFormat, dataList);
                log.info("已同步批次 {}，记录数: {}", page, dataList.size());
                page++;
            }

            // MODIFIED: 新增逻辑，将日期添加到所有日期集合中
            redisTemplate.opsForSet().add(ALL_COUPON_DATES_KEY, dateFormat);
            log.info("添加日期到集合 {}", dateFormat);

            log.info("数据同步完成，日期: {}", dateFormat);
        } catch (Exception e) {
            log.error("分批同步数据失败，日期: {}", dateFormat, e);
            throw e;
        }
    }

    /**
     * 同步单个批次的数据到Redis
     * @param dateFormat 日期格式字符串
     * @param dataList 数据列表
     */
    // MODIFIED: 完全重写方法实现，从List改为Hash
    private void syncBatchData(String dateFormat, List<BizCoupon> dataList) {
        try {
            String redisKey = getCouponHashKey(dateFormat);

            // 使用Redis Hash存储，field为couponId，value为JSON字符串
            Map<String, String> couponMap = dataList.stream()
                    .collect(Collectors.toMap(
                            coupon -> String.valueOf(coupon.getId()),
                            JSONUtil::toJsonStr
                    ));

            // 批量写入Hash
            redisTemplate.opsForHash().putAll(redisKey, couponMap);

            log.info("同步批次数据完成，日期: {}, 记录数: {}", dateFormat, dataList.size());
        } catch (Exception e) {
            log.error("JSON序列化或Redis操作失败，记录数: {}", dataList.size(), e);
            throw e;
        }
    }

    // MODIFIED: 新增方法，生成优惠券Hash的Redis键
    private String getCouponHashKey(String dateFormat) {
        return BIZ_COUPON_SECKILL_KEY + dateFormat;
    }

    // MODIFIED: 新增方法，从Redis中获取指定优惠券
    public BizCoupon getCouponFromRedis(String dateFormat, Long couponId) {
        String redisKey = getCouponHashKey(dateFormat);
        String couponJson = (String) redisTemplate.opsForHash().get(redisKey, String.valueOf(couponId));
        if (couponJson == null) {
            return null;
        }
        return JSONUtil.toBean(couponJson, BizCoupon.class);
    }

    // MODIFIED: 新增方法，更新Redis中的优惠券数据
    public void updateCouponInRedis(BizCoupon coupon) {
        String dateFormat = DateUtil.format(coupon.getInstantStart(), DATE_FORMAT);
        String redisKey = getCouponHashKey(dateFormat);

        // 更新Hash中的对应字段
        redisTemplate.opsForHash().put(
                redisKey,
                String.valueOf(coupon.getId()),
                JSONUtil.toJsonStr(coupon)
        );

        log.info("更新Redis优惠券数据: {}", coupon.getId());
    }

    // MODIFIED: 新增方法，获取指定日期的所有优惠券ID
    public Set<String> getCouponIdsByDate(String dateFormat) {
        String redisKey = getCouponHashKey(dateFormat);
        return redisTemplate.opsForHash().keys(redisKey)
                .stream()
                .map(Object::toString)
                .collect(Collectors.toSet());
    }

    // MODIFIED: 新增方法，获取所有存在优惠券数据的日期
    public Set<String> getAllCouponDates() {
        return redisTemplate.opsForSet().members(ALL_COUPON_DATES_KEY);
    }
}
